package config

import (
	"fmt"
	"github.com/cihub/seelog"
	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
	"gopkg.in/yaml.v2"
	"log"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"
)

func init() {
	GlobalTimestamp = time.Now().Format("2006-0102-1504-05.0000")
}

type Yaml struct {
	GroupList  []string `yaml:"group,flow"`
	HostList   []HostList
	HostFilter []string `yaml:"filter,flow"`
	Task       []Task   `yaml:"task,flow"`
}

type HostList struct {
	SshIp      string `yaml:"sship"`
	SshName    string `yaml:"sshname"`
	SshUser    string `yaml:"sshuser"`
	SshPw      string `yaml:"sshpassword"`
	SshType    string `yaml:"sshtype"` //认证方式：password or key
	SshKeyPath string `yaml:"sshkeypath"`
	SshGroup   string `yaml:"sshgroup"`
	SshPort    int    `yaml:"sshport"`
	LastResult string
	Client     *ssh.Client
	SftpClient *sftp.Client
	Session    *ssh.Session
	Include    bool
}

type Task struct {
	Shell  []Shell  `yaml:"shell,flow,omitempty"`
	Rsync  []Rsync  `yaml:"rsync,flow,omitempty"`
	Script []Script `yaml:"script,flow,omitempty"`
	File   []File   `yaml:"file,flow,omitempty"`
	Render []Render `yaml:"render,flow,omitempty"`
	Plugin []Plugin `yaml:"plugin,flow,omitempty"`
}

type Shell struct {
	Name string   `yaml:"name,omitempty"`
	Sudo bool     `yaml:"sudo,omitempty"`
	Cmd  []string `yaml:"cmd,omitempty"`
}

type Rsync struct {
	Sudo    bool   `yaml:"sudo,omitempty"`
	Name    string `yaml:"name,omitempty"`
	Src     string `yaml:"src,omitempty"`
	Dst     string `yaml:"dst,omitempty"`
	Temp    string `yaml:"temp,omitempty"`
	Delete  string `yaml:"delete,omitempty"`
	Exclude string `yaml:"exclude,omitempty"`
	BwLimit string `yaml:"bwlimit,omitempty"`
}

type File struct {
	Name    string `yaml:"name,omitempty"`
	Src     string `yaml:"src,omitempty"`
	Dst     string `yaml:"dst,omitempty"`
	Delete  bool   `yaml:"delete,omitempty"`
	Exclude string `yaml:"exclude,omitempty"`
	Temp    string `yaml:"temp,omitempty"`
	Buf     int    `yaml:"buf,omitempty"`
	Fetch   bool   `yaml:"fetch,omitempty"`
	Source  string `yaml:"source,omitempty"`
}

type Render struct {
	Name  string `yaml:"name,omitempty"`
	Src   string `yaml:"src,omitempty"`
	Dst   string `yaml:"dst,omitempty"`
	owner string `yaml:"dst,omitempty"`
	group string `yaml:"dst,omitempty"`
	mode  string `yaml:"dst,omitempty"`
	Temp  string `yaml:"temp,omitempty"`
}

type Script struct {
	Name  string   `yaml:"name,omitempty"`
	Sudo  bool     `yaml:"sudo,omitempty"`
	Chdir string   `yaml:"chdir,omitempty"`
	File  string   `yaml:"file,omitempty"`
	Env   string   `yaml:"env,omitempty"`
	Var   []string `yaml:"var,flow"`
}

type Plugin struct { //模块以目录命名，执行时将整个目录拷贝到目标服务器执行
	Name string   `yaml:"name,omitempty"`
	Sudo bool     `yaml:"sudo,omitempty"`
	Env  string   `yaml:"env,omitempty"`
	Var  []string `yaml:"var,flow"`
}

type KV struct {
	Name string `yaml:"name,omitempty"`
}

type SyncWaitGroupChannelType struct {
	HostWg      sync.WaitGroup
	ConnChannel chan struct{}
	CPUChannel  chan struct{}
	ConnNums    int
	CPUNums     int
}

var GlobalTimestamp, Level string
var HostVars map[string]interface{}

var LogConfig = `
<seelog type="asynctimer" asyncinterval="1000000" minlevel="LogInfo" maxlevel="error">
    <outputs formatid="main">
		 <filter levels="trace">
             <console formatid="colored-trace"/>
			 <rollingfile formatid="main" type="size" filename="./logs/log" maxsize="10485760" maxrolls="3" />
         </filter>
		 <filter levels="debug">
             <console formatid="colored-debug"/>
			 <rollingfile formatid="main" type="size" filename="./logs/log" maxsize="10485760" maxrolls="3" />
         </filter>
		 <filter levels="info">
             <console formatid="colored-info"/>
         </filter>
         <filter levels="warn">
             <console formatid="colored-warn"/>
			 <rollingfile formatid="main" type="size" filename="./logs/log" maxsize="10485760" maxrolls="3" />
         </filter>
         <filter levels="error,critical">
             <console formatid="colored-error"/>
			 <rollingfile formatid="main" type="size" filename="./logs/log" maxsize="10485760" maxrolls="3" />
         </filter>
    </outputs>
    <formats>
        <!-- 设置格式 -->
		<format id="colored-trace"  format="%EscM(36)[%LEV] [%Date(15:04:05.000)] %Msg%n%EscM(0)"/>
		<format id="colored-debug"  format="%EscM(34)[%LEV] [%Date(15:04:05.000)] %Msg%n%EscM(0)"/>
		<format id="colored-info"   format="%EscM(32)%Msg%n%EscM(0)"/>
        <format id="colored-warn"   format="%EscM(33)[%LEV] [%Date(15:04:05.000)] %Msg%n%EscM(0)"/>
        <format id="colored-error"  format="%EscM(31)[%LEV] [%Date(15:04:05.000)] %Msg%n%EscM(0)"/>
		<format id="main" format="[%LEV] [%Date(2006-01-02 15:04:05.000)] %Msg%n"/>
    </formats>
</seelog>`

func ParseConf(confFile *string, hostFile *string, group string, filter string, level string) (Params *Yaml) {
	Level = level
	LogConfig = strings.ReplaceAll(LogConfig, "LogInfo", Level)
	defer seelog.Flush()
	logger, _ := seelog.LoggerFromConfigAsBytes([]byte(LogConfig))
	_ = seelog.ReplaceLogger(logger)

	HostVars = make(map[string]interface{})

	seelog.Trace("Read config file ", *confFile)

	var config []byte
	var err error
	if *confFile == "" {
		config = []byte(defaultFile)
	} else {
		config, err = os.ReadFile(*confFile)
		if err != nil {
			fmt.Print(err)
		}
	}
	var Setting Yaml

	seelog.Trace("Unmarshal config file ", *confFile)
	err = yaml.Unmarshal(config, &Setting)

	if err != nil {
		log.Fatalf("Unmarshal: %v when to struct", err)
	}
	if group != "" {
		Setting.GroupList = strings.Split(group, ",")
		seelog.Trace(" Group ", Setting.GroupList)
	}

	//^生成主机列表,处理主机列表格式
	hostMap := HostFmt(hostFile)
	setHostVars()
	hostNum := 0

	for _, g := range hostMap.AllKeys() {

		if !matchGroup(Setting.GroupList, g) {
			continue
		}

		hostMapLen := len(hostMap.GetStringSlice(g))
		List := make([]HostList, hostMapLen, hostMapLen)

		hostNum = 0
		for hostNum < hostMapLen {

			sshGroup := hostMap.GetString(fmt.Sprintf("%s.%d.host_group", g, hostNum))

			hostName := hostMap.GetString(fmt.Sprintf("%s.%d.host_name", g, hostNum))
			if len(filter) > 0 {
				Setting.HostFilter = strings.Split(filter, ",")
			}

			if len(Setting.HostFilter) > 0 {
				List[hostNum].Include = MatchFilter(Setting.HostFilter, hostName)
			} else {
				List[hostNum].Include = true
			}

			ip := hostMap.GetString(fmt.Sprintf("%s.%d.host_ip", g, hostNum))
			user := hostMap.GetString(fmt.Sprintf("%s.%d.host_user", g, hostNum))
			pass := hostMap.GetString(fmt.Sprintf("%s.%d.host_pass", g, hostNum))
			sshType := hostMap.GetString(fmt.Sprintf("%s.%d.host_ssh_type", g, hostNum))
			sshkeyPath := hostMap.GetString(fmt.Sprintf("%s.%d.host_key_path", g, hostNum))
			sshPort := hostMap.GetInt(fmt.Sprintf("%s.%d.host_port", g, hostNum))

			List[hostNum].SshIp = ip
			List[hostNum].SshGroup = sshGroup

			if len(hostName) != 0 {
				List[hostNum].SshName = hostName
			}

			if len(sshkeyPath) != 0 {
				List[hostNum].SshKeyPath = sshkeyPath
			} else {
				List[hostNum].SshKeyPath = "~/.ssh/id_rsa"
			}

			if len(user) != 0 {
				List[hostNum].SshUser = user
			} else {
				List[hostNum].SshUser = "root"
			}

			if len(pass) != 0 {
				List[hostNum].SshPw = pass
			} else {
				List[hostNum].SshPw = "redhat"
			}

			if len(sshType) != 0 {
				List[hostNum].SshType = sshType
			} else {
				List[hostNum].SshType = "key"
			}

			if sshPort != 0 {
				List[hostNum].SshPort = sshPort
			} else {
				List[hostNum].SshPort = 22
			}
			hostNum++
		}
		Setting.HostList = append(Setting.HostList, List...)

	}

	//$生成主机列表结束

	return &Setting
}

func CreateClientAll(yaml *Yaml) {
	defer seelog.Flush()
	defer func() {
		if err := recover(); err != nil {
			_ = seelog.Errorf("连接错误: ", err)
			panic(err)
		}
	}()

	for _, server := range yaml.HostList {
		CreateClient(&server)
	}

}

func CreateClient(server *HostList) {
	defer seelog.Flush()
	var err error
	defer func() {
		if err := recover(); err != nil {
			_ = seelog.Errorf("连接错误: ", err)
			panic(err)
		}
	}()

	if server.Client != nil {
		return
	}

	sshConfig := ssh.ClientConfig{
		User:            server.SshUser,
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		Timeout:         time.Second * 10,
	}
	if server.SshType == "password" {
		sshConfig.Auth = []ssh.AuthMethod{ssh.Password(server.SshPw)}
	} else {
		key, readErr := os.ReadFile(server.SshKeyPath)
		check(readErr)
		signer, parseErr := ssh.ParsePrivateKey(key)
		check(parseErr)
		sshConfig.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
	}
	addr := fmt.Sprintf("%s:%d", server.SshIp, server.SshPort)

	if server.Client, err = ssh.Dial("tcp", addr, &sshConfig); err != nil {
		fmt.Println(server.SshIp, " ", err.Error())
		//panic(err)
	}
	if server.SftpClient, err = sftp.NewClient(server.Client); err != nil {
		fmt.Println(server.SshIp, " ", err.Error())
		//panic(err)
	}

}

func MatchFilter(filter []string, s string) (reg bool) {
	for _, f := range filter {
		reg, _ = regexp.MatchString(f, s)
		if reg {
			return reg
		}
	}
	return reg
}

func check(err error) {
	if err != nil {
		panic(err)
	}
}
